home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World 2008 September
/
PCWorld_2008-09_cd.bin
/
v cisle
/
sadanastroju
/
lightning-0.8-tb-win.xpi
/
js
/
calUtils.js
< prev
next >
Wrap
Text File
|
2008-03-02
|
85KB
|
2,253 lines
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Calendar component utils.
*
* The Initial Developer of the Original Code is
* Joey Minta <jminta@gmail.com>
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Philipp Kewisch <mozilla@kewis.ch>
* Daniel Boelzle <daniel.boelzle@sun.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* This file contains commonly used functions in a centralized place so that
* various components (and other js scopes) don't need to replicate them. Note
* that loading this file twice in the same scope will throw errors.
*/
/* Returns a clean new calIEvent */
function createEvent() {
return Components.classes["@mozilla.org/calendar/event;1"].
createInstance(Components.interfaces.calIEvent);
}
/* Returns a clean new calITodo */
function createTodo() {
return Components.classes["@mozilla.org/calendar/todo;1"].
createInstance(Components.interfaces.calITodo);
}
/* Returns a clean new calIDateTime */
function createDateTime() {
return Components.classes["@mozilla.org/calendar/datetime;1"].
createInstance(Components.interfaces.calIDateTime);
}
/* Returns a clean new calIRecurrenceInfo */
function createRecurrenceInfo(aItem) {
var recInfo = Components.classes["@mozilla.org/calendar/recurrence-info;1"].
createInstance(Components.interfaces.calIRecurrenceInfo);
recInfo.item = aItem;
return recInfo;
}
/* Returns a clean new calIRecurrenceRule */
function createRecurrenceRule() {
return Components.classes["@mozilla.org/calendar/recurrence-rule;1"].
createInstance(Components.interfaces.calIRecurrenceRule);
}
/* Returns a clean new calIAttendee */
function createAttendee() {
return Components.classes["@mozilla.org/calendar/attendee;1"].
createInstance(Components.interfaces.calIAttendee);
}
/* Shortcut to the io service */
function getIOService() {
if (getIOService.mObject === undefined) {
getIOService.mObject = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService2);
}
return getIOService.mObject;
}
/* Shortcut to the calendar-manager service */
function getCalendarManager() {
if (getCalendarManager.mObject === undefined) {
getCalendarManager.mObject = Components.classes["@mozilla.org/calendar/manager;1"]
.getService(Components.interfaces.calICalendarManager);
}
return getCalendarManager.mObject;
}
/* Shortcut to the ICS service */
function getIcsService() {
if (getIcsService.mObject === undefined) {
getIcsService.mObject = Components.classes["@mozilla.org/calendar/ics-service;1"]
.getService(Components.interfaces.calIICSService);
}
return getIcsService.mObject;
}
/* Shortcut to the timezone service */
function getTimezoneService() {
if (getTimezoneService.mObject === undefined) {
getTimezoneService.mObject = Components.classes["@mozilla.org/calendar/timezone-service;1"]
.getService(Components.interfaces.calITimezoneService);
}
return getTimezoneService.mObject;
}
/* Shortcut to calendar search service */
function getCalendarSearchService() {
if (getCalendarSearchService.mObject === undefined) {
getCalendarSearchService.mObject = Components.classes["@mozilla.org/calendar/calendarsearch-service;1"]
.getService(Components.interfaces.calICalendarSearchProvider);
}
return getCalendarSearchService.mObject;
}
/// @return the UTC timezone.
function UTC() {
if (UTC.mObject === undefined) {
UTC.mObject = getTimezoneService().UTC;
}
return UTC.mObject;
}
/// @return the floating timezone.
function floating() {
if (floating.mObject === undefined) {
floating.mObject = getTimezoneService().floating;
}
return floating.mObject;
}
/**
* Function to get the (cached) best guess at a user's default timezone. We'll
* use the value of the calendar.timezone.local preference, if it exists. If
* not, we'll do our best guess.
*
* @return user's default timezone.
*/
function calendarDefaultTimezone() {
if (calendarDefaultTimezone.mTz === undefined) {
var prefTzid = getPrefSafe("calendar.timezone.local", null);
var tzid = prefTzid;
if (!tzid) {
tzid = guessSystemTimezone();
}
calendarDefaultTimezone.mTz = getTimezoneService().getTimezone(tzid);
ASSERT(calendarDefaultTimezone.mTz, "timezone not found: " + tzid);
// Update prefs if necessary:
if (calendarDefaultTimezone.mTz && calendarDefaultTimezone.mTz.tzid != prefTzid) {
setPref("calendar.timezone.local", "CHAR", calendarDefaultTimezone.mTz.tzid);
}
}
return calendarDefaultTimezone.mTz;
}
/**
* Format the given string to work inside a CSS rule selector
* (and as part of a non-unicode preference key).
*
* Replaces each space ' ' char with '_'.
* Replaces each char other than ascii digits and letters, with '-uxHHH-'
* where HHH is unicode in hexadecimal (variable length, terminated by the '-').
*
* Ensures: result only contains ascii digits, letters,'-', and '_'.
* Ensures: result is invertible, so (f(a) = f(b)) implies (a = b).
* also means f is not idempotent, so (a != f(a)) implies (f(a) != f(f(a))).
* Ensures: result must be lowercase.
* Rationale: preference keys require 8bit chars, and ascii chars are legible
* in most fonts (in case user edits PROFILE/prefs.js).
* CSS class names in Gecko 1.8 seem to require lowercase,
* no punctuation, and of course no spaces.
* nmchar [_a-zA-Z0-9-]|{nonascii}|{escape}
* name {nmchar}+
* http://www.w3.org/TR/CSS21/grammar.html#scanner
*
* @param aString The unicode string to format
* @return The formatted string using only chars [_a-zA-Z0-9-]
*/
function formatStringForCSSRule(aString) {
function toReplacement(ch) {
// char code is natural number (positive integer)
var nat = ch.charCodeAt(0);
switch(nat) {
case 0x20: // space
return "_";
default:
return "-ux" + nat.toString(16) + "-"; // lowercase
}
}
// Result must be lowercase or style rule will not work.
return aString.toLowerCase().replace(/[^a-zA-Z0-9]/g, toReplacement);
}
/**
* We're going to do everything in our power, short of rumaging through the
* user's actual file-system, to figure out the time-zone they're in. The
* deciding factors are the offsets given by (northern-hemisphere) summer and
* winter JSdates. However, when available, we also use the name of the
* timezone in the JSdate, or a string-bundle term from the locale.
*
* @return a mozilla ICS timezone string.
*/
function guessSystemTimezone() {
// Probe JSDates for basic OS timezone offsets and names.
const dateJun = (new Date(2005, 5,20)).toString();
const dateDec = (new Date(2005,11,20)).toString();
const tzNameRegex = /[^(]* ([^ ]*) \(([^)]+)\)/;
const nameDataJun = dateJun.match(tzNameRegex);
const nameDataDec = dateDec.match(tzNameRegex);
const tzNameJun = nameDataJun && nameDataJun[2];
const tzNameDec = nameDataDec && nameDataDec[2];
const offsetRegex = /[+-]\d{4}/;
const offsetJun = dateJun.match(offsetRegex)[0];
const offsetDec = dateDec.match(offsetRegex)[0];
const tzSvc = getTimezoneService();
function getIcalString(component, property) {
return (component &&
component.getFirstProperty(property).valueAsIcalString);
}
// Check if Olson ZoneInfo timezone matches OS/JSDate timezone properties:
// * standard offset and daylight/summer offset if present (longitude),
// * if has summer time, direction of change (northern/southern hemisphere)
// * if has summer time, dates of next transitions
// * timezone name (such as "Western European Standard Time").
// Score is 3 if matches dates and names, 2 if matches dates without names,
// 1 if matches dates within a week (so changes on different weekday),
// otherwise 0 if no match.
function checkTZ(tzId) {
var tz = tzSvc.getTimezone(tzId);
// Have to handle UTC separately because it has no .component.
if (tz.isUTC) {
if (offsetDec == 0 && offsetJun == 0) {
if (tzNameJun == "UTC" && tzNameDec == "UTC") {
return 3;
} else {
return 2;
}
} else {
return 0;
}
}
var subComp = tz.component;
// find currently applicable time period, not just first,
// because offsets of timezone may be changed over the years.
var standard = findCurrentTimePeriod(tz, subComp, "STANDARD");
var standardTZOffset = getIcalString(standard, "TZOFFSETTO");
var standardName = getIcalString(standard, "TZNAME");
var daylight = findCurrentTimePeriod(tz, subComp, "DAYLIGHT");
var daylightTZOffset = getIcalString(daylight, "TZOFFSETTO");
var daylightName = getIcalString(daylight, "TZNAME");
// Try northern hemisphere cases.
if (offsetDec == standardTZOffset && offsetDec == offsetJun &&
!daylight) {
if (standardName && standardName == tzNameJun) {
return 3;
} else {
return 2;
}
}
if (offsetDec == standardTZOffset && offsetJun == daylightTZOffset &&
daylight) {
var dateMatchWt = systemTZMatchesTimeShiftDates(tz, subComp);
if (dateMatchWt > 0) {
if (standardName && standardName == tzNameJun &&
daylightName && daylightName == tzNameDec) {
return 3;
} else {
return dateMatchWt;
}
}
}
// Now flip them and check again, to cover southern hemisphere cases.
if (offsetJun == standardTZOffset && offsetDec == offsetJun &&
!daylight) {
if (standardName && standardName == tzNameDec) {
return 3;
} else {
return 2;
}
}
if (offsetJun == standardTZOffset && offsetDec == daylightTZOffset &&
daylight) {
var dateMatchWt = systemTZMatchesTimeShiftDates(tz, subComp);
if (dateMatchWt > 0) {
if (standardName && standardName == tzNameJun &&
daylightName && daylightName == tzNameDec) {
return 3;
} else {
return dateMatchWt;
}
}
}
return 0;
}
// returns 2=match-within-hours, 1=match-within-week, 0=no-match
function systemTZMatchesTimeShiftDates(tz, subComp) {
// Verify local autumn and spring shifts also occur in system timezone
// (jsDate) on correct date in correct direction.
// (Differs for northern/southern hemisphere.
// Local autumn shift is to local winter STANDARD time.
// Local spring shift is to local summer DAYLIGHT time.)
const autumnShiftJSDate =
findCurrentTimePeriod(tz, subComp, "STANDARD", true);
const afterAutumnShiftJSDate = new Date(autumnShiftJSDate);
const beforeAutumnShiftJSDate = new Date(autumnShiftJSDate);
const springShiftJSDate =
findCurrentTimePeriod(tz, subComp, "DAYLIGHT", true);
const beforeSpringShiftJSDate = new Date(springShiftJSDate);
const afterSpringShiftJSDate = new Date(springShiftJSDate);
// Try with 6 HOURS fuzz in either direction, since OS and ZoneInfo
// may disagree on the exact time of shift (midnight, 2am, 4am, etc).
beforeAutumnShiftJSDate.setHours(autumnShiftJSDate.getHours()-6);
afterAutumnShiftJSDate.setHours(autumnShiftJSDate.getHours()+6);
afterSpringShiftJSDate.setHours(afterSpringShiftJSDate.getHours()+6);
beforeSpringShiftJSDate.setHours(beforeSpringShiftJSDate.getHours()-6);
if ((beforeAutumnShiftJSDate.getTimezoneOffset() <
afterAutumnShiftJSDate.getTimezoneOffset()) &&
(beforeSpringShiftJSDate.getTimezoneOffset() >
afterSpringShiftJSDate.getTimezoneOffset())) {
return 2;
}
// Try with 7 DAYS fuzz in either direction, so if no other tz found,
// will have a nearby tz that disagrees only on the weekday of shift
// (sunday vs. friday vs. calendar day), or off by exactly one week,
// (e.g., needed to guess Africa/Cairo on w2k in 2006).
beforeAutumnShiftJSDate.setDate(autumnShiftJSDate.getDate()-7);
afterAutumnShiftJSDate.setDate(autumnShiftJSDate.getDate()+7);
afterSpringShiftJSDate.setDate(afterSpringShiftJSDate.getDate()+7);
beforeSpringShiftJSDate.setDate(beforeSpringShiftJSDate.getDate()-7);
if ((beforeAutumnShiftJSDate.getTimezoneOffset() <
afterAutumnShiftJSDate.getTimezoneOffset()) &&
(beforeSpringShiftJSDate.getTimezoneOffset() >
afterSpringShiftJSDate.getTimezoneOffset())) {
return 1;
}
// no match
return 0;
}
const todayUTC = createDateTime(); todayUTC.jsDate = new Date();
const oneYrUTC = todayUTC.clone(); oneYrUTC.year += 1;
const periodStartCalDate = createDateTime();
const periodUntilCalDate = createDateTime(); // until timezone is UTC
const periodCalRule =
Components.classes["@mozilla.org/calendar/recurrence-rule;1"]
.createInstance(Components.interfaces.calIRecurrenceRule);
const untilRegex = /UNTIL=(\d{8}T\d{6}Z)/;
function findCurrentTimePeriod(tz, subComp, standardOrDaylight,
isForNextTransitionDate) {
periodStartCalDate.timezone = tz;
// Iterate through 'STANDARD' declarations or 'DAYLIGHT' declarations
// (periods in history with different settings.
// e.g., US changes daylight start in 2007 (from April to March).)
// Each period is marked by a DTSTART.
// Find the currently applicable period: has most recent DTSTART
// not later than today and no UNTIL, or UNTIL is greater than today.
for (var period = subComp.getFirstSubcomponent(standardOrDaylight);
period;
period = subComp.getNextSubcomponent(standardOrDaylight)) {
periodStartCalDate.icalString = getIcalString(period, "DTSTART");
if (oneYrUTC.nativeTime < periodStartCalDate.nativeTime) {
continue; // period starts too far in future
}
// Must examine UNTIL date (not next daylight start) because
// some zones (e.g., Arizona, Hawaii) may stop using daylight
// time, so there might not be a next daylight start.
var rrule = period.getFirstProperty("RRULE");
if (rrule) {
var match = untilRegex.exec(rrule.valueAsIcalString);
if (match) {
periodUntilCalDate.icalString = match[1];
if (todayUTC.nativeTime > periodUntilDate.nativeTime) {
continue; // period ends too early
}
} // else forever rule
} // else no daylight rule
// found period that covers today.
if (!isForNextTransitionDate) {
return period;
} else /*isForNextTranstionDate*/ {
if (todayUTC.nativeTime < periodStartCalDate.nativeTime) {
// already know periodStartCalDate < oneYr from now,
// and transitions are at most once per year, so it is next.
return periodStartCalDate.jsDate;
} else if (rrule) {
// find next occurrence after today
periodCalRule.icalProperty = rrule;
var nextTransitionDate =
periodCalRule.getNextOccurrence(periodStartCalDate,
todayUTC);
// make sure rule doesn't end before next transition date.
if (nextTransitionDate)
return nextTransitionDate.jsDate;
}
}
}
// no such period found
return null;
}
// Try to find a tz that matches OS/JSDate timezone. If no name match,
// will use first of probable timezone(s) with highest score.
var probableTZId = "floating"; // default fallback tz if no tz matches.
var probableTZScore = 0;
var probableTZSource = null;
const sbSvc =
Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService);
const calProperties =
sbSvc.createBundle("chrome://calendar/locale/calendar.properties");
// First, try to detect operating system timezone.
try {
var osUserTimeZone = null;
var zoneInfoIdFromOSUserTimeZone = null;
if (navigator.oscpu.match(/^Windows/)) {
var regOSName, fileOSName;
if (navigator.oscpu.match(/^Windows NT/)) {
regOSName = "Windows NT";
fileOSName = "WindowsNT";
} else {
// Note: windows 98 compatibility will be deleted
// in releases built on Gecko 1.9 or later.
regOSName = "Windows";
fileOSName = "Windows98";
}
// If on Windows NT (2K/XP/Vista), current timezone only lists its
// localized name, so to find its registry key name, match localized
// name to localized names of each windows timezone listed in
// registry. Then use the registry key name to see if this
// timezone has a known ZoneInfo name.
var wrk = (Components
.classes["@mozilla.org/windows-registry-key;1"]
.createInstance(Components.interfaces.nsIWindowsRegKey));
wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation",
wrk.ACCESS_READ);
var currentTZStandardName = wrk.readStringValue("StandardName");
wrk.close()
wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
("SOFTWARE\\Microsoft\\"+regOSName+
"\\CurrentVersion\\Time Zones"),
wrk.ACCESS_READ);
// Linear search matching localized name of standard timezone
// to find the non-localized registry key.
// (Registry keys are sorted by subkeyName, not by localized name
// nor offset, so cannot use binary search.)
for (var i = 0; i < wrk.childCount; i++) {
var subkeyName = wrk.getChildName(i);
var subkey = wrk.openChild(subkeyName, wrk.ACCESS_READ);
var std = subkey.readStringValue("Std");
subkey.close();
if (std == currentTZStandardName) {
osUserTimeZone = subkeyName;
break;
}
}
wrk.close();
if (osUserTimeZone != null) {
// Lookup timezone registry key in table of known tz keys
// to convert to ZoneInfo timezone id.
const regKeyToZoneInfoBundle =
sbSvc.createBundle("chrome://calendar/content/"+
fileOSName+"ToZoneInfoTZId.properties");
zoneInfoIdFromOSUserTimeZone =
regKeyToZoneInfoBundle.GetStringFromName(osUserTimeZone);
}
} else {
// Else look for ZoneInfo timezone id in
// - TZ environment variable value
// - /etc/localtime symbolic link target path
// - /etc/TIMEZONE or /etc/timezone file content
// - /etc/sysconfig/clock file line content.
// The timezone is set per user via the TZ environment variable.
// TZ may contain a path that may start with a colon and ends with
// a ZoneInfo timezone identifier, such as ":America/New_York" or
// ":/share/lib/zoneinfo/America/New_York". The others are
// in the filesystem so they give one timezone for the system;
// the values are similar (but cannot have a leading colon).
// (Note: the OS ZoneInfo database may be a different version from
// the one we use, so still need to check that DST dates match.)
var continent = "Africa|America|Antarctica|Asia|Australia|Europe";
var ocean = "Arctic|Atlantic|Indian|Pacific";
var tzRegex = new RegExp(".*((?:"+continent+"|"+ocean+")"+
"(?:[/][-A-Z_a-z]+)+)");
const CC = Components.classes;
const CI = Components.interfaces;
var envSvc = (CC["@mozilla.org/process/environment;1"]
.getService(Components.interfaces.nsIEnvironment));
function environmentVariableValue(varName) {
var value = envSvc.get(varName);
if (!value) return "";
if (!value.match(tzRegex)) return "";
return varName+"="+value;
}
function symbolicLinkTarget(filepath) {
try {
var file = (CC["@mozilla.org/file/local;1"]
.createInstance(CI.nsILocalFile));
file.initWithPath(filepath);
file.QueryInterface(CI.nsIFile);
if (!file.exists()) return "";
if (!file.isSymlink()) return "";
if (!file.target.match(tzRegex)) return "";
return filepath +" -> "+file.target;
} catch (ex) {
Components.utils.reportError(filepath+": "+ex);
return "";
}
}
function fileFirstZoneLineString(filepath) {
// return first line of file that matches tzRegex (ZoneInfo id),
// or "" if no file or no matching line.
try {
var file = (CC["@mozilla.org/file/local;1"]
.createInstance(CI.nsILocalFile));
file.initWithPath(filepath);
file.QueryInterface(CI.nsIFile);
if (!file.exists()) return "";
var fileInstream =
(CC["@mozilla.org/network/file-input-stream;1"].
createInstance(CI.nsIFileInputStream));
const PR_RDONLY = 0x1;
fileInstream.init(file, PR_RDONLY, 0, 0);
fileInstream.QueryInterface(CI.nsILineInputStream);
try {
var line = {}, hasMore = true, MAXLINES = 10;
for (var i = 0; hasMore && i < MAXLINES; i++) {
hasMore = fileInstream.readLine(line);
if (line.value && line.value.match(tzRegex)) {
return filepath+": "+line.value;
}
}
return ""; // not found
} finally {
fileInstream.close();
}
} catch (ex) {
Components.utils.reportError(filepath+": "+ex);
return "";
}
}
osUserTimeZone = (environmentVariableValue("TZ") ||
symbolicLinkTarget("/etc/localtime") ||
fileFirstZoneLineString("/etc/TIMEZONE") ||
fileFirstZoneLineString("/etc/timezone") ||
fileFirstZoneLineString("/etc/sysconfig/clock"));
var results = osUserTimeZone.match(tzRegex);
if (results) {
zoneInfoIdFromOSUserTimeZone = results[1];
}
}
// check how well OS tz matches tz defined in our version of zoneinfo db
if (zoneInfoIdFromOSUserTimeZone != null) {
var tzId = tzSvc.tzidPrefix + zoneInfoIdFromOSUserTimeZone;
var score = checkTZ(tzId);
switch(score) {
case 0:
// Did not match.
// Maybe OS or Application is old, and the timezone changed.
// Or maybe user turned off DST in Date/Time control panel.
// Will look for a better matching tz, or fallback to floating.
// (Match OS so alarms go off at time indicated by OS clock.)
const consoleSvc =
(Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService));
var msg = (calProperties.formatStringFromName
("WarningOSTZNoMatch",
[osUserTimeZone, zoneInfoIdFromOSUserTimeZone], 2));
consoleSvc.logStringMessage(msg);
break;
case 1: case 2:
// inexact match: OS TZ and our ZoneInfo TZ matched imperfectly.
// Will keep looking, will use tzId unless another is better.
// (maybe OS TZ has changed to match a nearby TZ, so maybe
// another ZoneInfo TZ matches it better).
probableTZId = tzId;
probableTZScore = score;
probableTZSource = (calProperties.formatStringFromName
("TZFromOS", [osUserTimeZone], 1));
break;
case 3:
// exact match
return tzId;
}
}
} catch (ex) {
// zoneInfo id given was not recognized by our ZoneInfo database
var errMsg = (calProperties.formatStringFromName
("SkippingOSTimezone",
[zoneInfoIdFromOSUserTimeZone || osUserTimeZone], 1));
Components.utils.reportError(errMsg+" "+ex);
}
// Second, give priority to "likelyTimezone"s if provided by locale.
try {
// The likelyTimezone property is a comma-separated list of
// ZoneInfo timezone ids.
const bundleTZString =
calProperties.GetStringFromName("likelyTimezone");
const bundleTZIds = bundleTZString.split(/\s*,\s*/);
for each (var bareTZId in bundleTZIds) {
var tzId = bareTZId;
if (tzId.indexOf("/mozilla.org/") == -1) {
// Convert a ZoneInfo timezone to a mozilla timezone-string
tzId = tzSvc.tzidPrefix + tzId;
}
try {
var score = checkTZ(tzId);
switch (score) {
case 0:
break;
case 1: case 2:
if (score > probableTZScore) {
probableTZId = tzId;
probableTZScore = score;
probableTZSource = (calProperties.GetStringFromName
("TZFromLocale"));
}
break;
case 3:
return tzId;
}
} catch (ex) {
var errMsg = (calProperties.formatStringFromName
("SkippingLocaleTimezone", [bareTZId], 1));
Components.utils.reportError(errMsg+" "+ex);
}
}
} catch (ex) { // Oh well, this didn't work, next option...
Components.utils.reportError(ex);
}
// Third, try all known timezones.
const tzIDs = tzSvc.timezoneIds;
while (tzIDs.hasMore()) {
var tzId = tzIDs.getNext();
try {
var score = checkTZ(tzId);
switch(score) {
case 0: break;
case 1: case 2:
if (score > probableTZScore) {
probableTZId = tzId;
probableTZScore = score;
probableTZSource = (calProperties.GetStringFromName
("TZFromKnownTimezones"));
}
break;
case 3:
return tzId;
}
} catch (ex) { // bug if ics service doesn't recognize own tzid!
var msg = ("ics-service doesn't recognize own tzid: "+tzId+"\n"+
ex);
Components.utils.reportError(msg);
}
}
// If reach here, there were no score=3 matches, so Warn in console.
try {
const jsConsole = Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService);
switch(probableTZScore) {
case 0:
jsConsole.logStringMessage(calProperties.GetStringFromName
("warningUsingFloatingTZNoMatch"));
break;
case 1: case 2:
var tzId = probableTZId;
var tz = tzSvc.getTimezone(tzId);
var subComp = tz.component;
var standard = findCurrentTimePeriod(tz, subComp, "STANDARD");
var standardTZOffset = getIcalString(standard, "TZOFFSETTO");
var daylight = findCurrentTimePeriod(tz, subComp, "DAYLIGHT");
var daylightTZOffset = getIcalString(daylight, "TZOFFSETTO");
var warningDetail;
if (probableTZScore == 1) {
// score 1 means has daylight time,
// but transitions start on different weekday from os timezone.
function weekday(icsDate) {
var calDate = createDateTime();
calDate.timezone = tz;
calDate.icalString = icsDate;
return calDate.jsDate.toLocaleFormat("%a");
}
var standardStart = getIcalString(standard, "DTSTART");
var standardStartWeekday = weekday(standardStart);
var standardRule = getIcalString(standard, "RRULE");
var standardText =
(" Standard: "+standardStart+" "+standardStartWeekday+"\n"+
" "+standardRule+"\n");
var daylightStart = getIcalString(daylight, "DTSTART");
var daylightStartWeekday = weekday(daylightStart);
var daylightRule = getIcalString(daylight, "RRULE");
var daylightText =
(" Daylight: "+daylightStart+" "+daylightStartWeekday+"\n"+
" "+daylightRule+"\n");
warningDetail =
((standardStart < daylightStart
? standardText + daylightText
: daylightText + standardText)+
(calProperties.GetStringFromName
("TZAlmostMatchesOSDifferAtMostAWeek")));
} else {
warningDetail =
(calProperties.GetStringFromName("TZSeemsToMatchOS"));
}
var offsetString = (standardTZOffset+
(!daylightTZOffset? "": "/"+daylightTZOffset));
var warningMsg = (calProperties.formatStringFromName
("WarningUsingGuessedTZ",
[tzId, offsetString, warningDetail,
probableTZSource], 4));
jsConsole.logStringMessage(warningMsg);
break;
}
} catch (ex) { // don't abort if error occurs warning user
Components.utils.reportError(ex);
}
// return the guessed timezone
return probableTZId;
}
/**
* Shared dialog functions
* Gets the calendar directory, defaults to <profile-dir>/calendar
*/
function getCalendarDirectory() {
if (getCalendarDirectory.mDir === undefined) {
var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties);
var dir = dirSvc.get("ProfD", Components.interfaces.nsILocalFile);
dir.append("calendar-data");
if (!dir.exists()) {
try {
dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0700);
} catch (exc) {
ASSERT(false, exc);
throw exc;
}
}
getCalendarDirectory.mDir = dir;
}
return getCalendarDirectory.mDir.clone();
}
/**
* Check if the specified calendar is writable. This is the case when it is not
* marked readOnly, we are not offline, or we are offline and the calendar is
* local.
*
* @param aCalendar The calendar to check
* @return True if the calendar is writable
*/
function isCalendarWritable(aCalendar) {
return (!aCalendar.readOnly &&
(!getIOService().offline ||
aCalendar.getProperty("requiresNetwork") === false));
}
/**
* Opens the Create Calendar wizard
*
* @param aCallback a function to be performed after calendar creation
*/
function openCalendarWizard(aCallback) {
openDialog("chrome://calendar/content/calendarCreation.xul", "caEditServer",
"chrome,titlebar,modal", aCallback);
}
/**
* Opens the calendar properties window for aCalendar
*
* @param aCalendar the calendar whose properties should be displayed
* @param aCallback function that should be run when the dialog is accepted
*/
function openCalendarProperties(aCalendar, aCallback) {
openDialog("chrome://calendar/content/calendarProperties.xul",
"caEditServer", "chrome,titlebar,modal",
{calendar: aCalendar, onOk: aCallback});
}
/**
* Opens the print dialog
*/
function calPrint() {
openDialog("chrome://calendar/content/printDialog.xul", "Print",
"centerscreen,chrome,resizable");
}
/**
* Other functions
*/
/**
* Takes a string and returns an nsIURI
*
* @param aUriString the string of the address to for the spec of the nsIURI
*
* @returns an nsIURI whose spec is aUriString
*/
function makeURL(aUriString) {
var ioSvc = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
return ioSvc.newURI(aUriString, null, null);
}
/**
* Returns a calIDateTime that corresponds to the current time in the user's
* default timezone.
*/
function now() {
var d = createDateTime();
d.jsDate = new Date();
return d.getInTimezone(calendarDefaultTimezone());
}
/**
* Returns a calIDateTime corresponding to a javascript Date.
*
* @param aDate a javascript date
* @param aTimezone (optional) a timezone that should be enforced
* @returns a calIDateTime
*
* @warning Use of this function is strongly discouraged. calIDateTime should
* be used directly whenever possible.
* If you pass a timezone, then the passed jsDate's timezone will be ignored,
* but only its local time portions are be taken.
*/
function jsDateToDateTime(aDate, aTimezone) {
var newDate = createDateTime();
if (aTimezone) {
newDate.resetTo(aDate.getFullYear(),
aDate.getMonth(),
aDate.getDate(),
aDate.getHours(),
aDate.getMinutes(),
aDate.getSeconds(),
aTimezone);
} else {
newDate.jsDate = aDate;
}
return newDate;
}
/**
* Selects an item with id aItemId in the radio group with id aRadioGroupId
*
* @param aRadioGroupId the id of the radio group which contains the item
* @param aItemId the item to be selected
*/
function calRadioGroupSelectItem(aRadioGroupId, aItemId) {
var radioGroup = document.getElementById(aRadioGroupId);
var items = radioGroup.getElementsByTagName("radio");
var index;
for (var i in items) {
if (items[i].getAttribute("id") == aItemId) {
index = i;
break;
}
}
ASSERT(index && index != 0, "Can't find radioGroup item to select.", true);
radioGroup.selectedIndex = index;
}
/** checks if an item is supported by a Calendar
* @param aCalendar the calendar
* @param aItem the item either a task or an event
* @return true or false
*/
function isItemSupported(aItem, aCalendar) {
if (isToDo(aItem)) {
return (aCalendar.getProperty("capabilities.tasks.supported") !== false);
} else if (isEvent(aItem)) {
return (aCalendar.getProperty("capabilities.events.supported") !== false);
}
return false;
}
/**
* Determines whether or not the aObject is a calIEvent
*
* @param aObject the object to test
* @returns true if the object is a calIEvent, false otherwise
*/
function isEvent(aObject) {
return aObject instanceof Components.interfaces.calIEvent;
}
/**
* Determines whether or not the aObject is a calITodo
*
* @param aObject the object to test
* @returns true if the object is a calITodo, false otherwise
*/
function isToDo(aObject) {
return aObject instanceof Components.interfaces.calITodo;
}
/**
* Normal get*Pref calls will throw if the pref is undefined. This function
* will get a bool, int, or string pref. If the pref is undefined, it will
* return aDefault.
*
* @param aPrefName the (full) name of preference to get
* @param aDefault (optional) the value to return if the pref is undefined
*/
function getPrefSafe(aPrefName, aDefault) {
const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
const prefB = Components.classes["@mozilla.org/preferences-service;1"]
.getService(nsIPrefBranch);
// Since bug 193332 does not fix the current branch, calling get*Pref will
// throw NS_ERROR_UNEXPECTED if clearUserPref() was called and there is no
// default value. To work around that, catch the exception.
try {
switch (prefB.getPrefType(aPrefName)) {
case nsIPrefBranch.PREF_BOOL:
return prefB.getBoolPref(aPrefName);
case nsIPrefBranch.PREF_INT:
return prefB.getIntPref(aPrefName);
case nsIPrefBranch.PREF_STRING:
return prefB.getCharPref(aPrefName);
default: // includes nsIPrefBranch.PREF_INVALID
return aDefault;
}
} catch (e) {
return aDefault;
}
}
/**
* Wrapper for setting prefs of various types
*
* @param aPrefName the (full) name of preference to set
* @param aPrefType the type of preference to set. Valid valuse are:
BOOL, INT, and CHAR
* @param aPrefValue the value to set the pref to
*/
function setPref(aPrefName, aPrefType, aPrefValue) {
const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
const prefB = Components.classes["@mozilla.org/preferences-service;1"]
.getService(nsIPrefBranch);
switch (aPrefType) {
case "BOOL":
prefB.setBoolPref(aPrefName, aPrefValue);
break;
case "INT":
prefB.setIntPref(aPrefName, aPrefValue);
break;
case "CHAR":
prefB.setCharPref(aPrefName, aPrefValue);
break;
}
}
/**
* Helper function to set a localized (complex) pref from a given string
*
* @param aPrefName the (full) name of preference to set
* @param aString the string to which the preference value should be set
*/
function setLocalizedPref(aPrefName, aString) {
const prefB = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
var str = Components.classes["@mozilla.org/supports-string;1"].
createInstance(Components.interfaces.nsISupportsString);
str.data = aString;
prefB.setComplexValue(aPrefName, Components.interfaces.nsISupportsString, str);
}
/**
* Like getPrefSafe, except for complex prefs (those used for localized data).
*
* @param aPrefName the (full) name of preference to get
* @param aDefault (optional) the value to return if the pref is undefined
*/
function getLocalizedPref(aPrefName, aDefault) {
const pb2 = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch2);
var result;
try {
result = pb2.getComplexValue(aPrefName, Components.interfaces.nsISupportsString).data;
} catch(ex) {
return aDefault;
}
return result;
}
/**
* Get array of category names from preferences or locale default,
* unescaping any commas in each category name.
* @return array of category names
*/
function getPrefCategoriesArray() {
var categories = getLocalizedPref("calendar.categories.names", null);
// If no categories are configured load a default set from properties file
if (!categories || categories == "") {
categories = calGetString("categories", "categories");
setLocalizedPref("calendar.categories.names", categories);
}
return categoriesStringToArray(categories);
}
/**
* Convert categories string to list of category names.
*
* Stored categories may include escaped commas within a name.
* Split categories string at commas, but not at escaped commas (\,).
* Afterward, replace escaped commas (\,) with commas (,) in each name.
* @param aCategoriesPrefValue string from "calendar.categories.names" pref,
* which may contain escaped commas (\,) in names.
* @return list of category names
*/
function categoriesStringToArray(aCategories) {
// \u001A is the unicode "SUBSTITUTE" character
function revertCommas(name) { return name.replace(/\u001A/g, ","); }
return aCategories.replace(/\\,/g, "\u001A").split(",").map(revertCommas);
}
/**
* Set categories preference, escaping any commas in category names.
* @param aCategoriesArray array of category names,
* may contain unescaped commas which will be escaped in combined pref.
*/
function setPrefCategoriesFromArray(aCategoriesArray) {
setLocalizedPref("calendar.categories.names",
categoriesArrayToString(aCategoriesList));
}
/**
* Convert array of category names to string.
*
* Category names may contain commas (,). Escape commas (\,) in each,
* then join them in comma separated string for storage.
* @param aSortedCategoriesArray sorted array of category names,
* may contain unescaped commas, which will be escaped in combined string.
*/
function categoriesArrayToString(aSortedCategoriesArray) {
function escapeComma(category) { return category.replace(/,/g,"\\,"); }
return aSortedCategoriesArray.map(escapeComma).join(",");
}
/**
* Sort an array of strings according to the current locale.
* Modifies aStringArray, returning it sorted.
*/
function sortArrayByLocaleCollator(aStringArray) {
// get a current locale string collator for compareEvents
var localeService =
Components
.classes["@mozilla.org/intl/nslocaleservice;1"]
.getService(Components.interfaces.nsILocaleService);
var localeCollator =
Components
.classes["@mozilla.org/intl/collation-factory;1"]
.getService(Components.interfaces.nsICollationFactory)
.CreateCollation(localeService.getApplicationLocale());
function compare(a, b) { return localeCollator.compareString(0, a, b); }
aStringArray.sort(compare);
return aStringArray;
}
/**
* Gets the value of a string in a .properties file
*
* @param aBundleName the name of the properties file. It is assumed that the
* file lives in chrome://calendar/locale/
* @param aStringName the name of the string within the properties file
* @param aParams optional array of parameters to format the string
*/
function calGetString(aBundleName, aStringName, aParams) {
try {
var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService);
var props = sbs.createBundle("chrome://calendar/locale/"+aBundleName+".properties");
if (aParams && aParams.length) {
return props.formatStringFromName(aStringName, aParams, aParams.length);
} else {
return props.GetStringFromName(aStringName);
}
} catch (ex) {
var s = "Failed to read '" + aStringName + "' from " +
"'chrome://calendar/locale/" + aBundleName + ".properties'.";
Components.utils.reportError(s + " Error: " + ex);
return s;
}
}
/** Returns a best effort at making a UUID. If we have the UUIDGenerator
* service available, we'll use that. If we're somewhere where it doesn't
* exist, like Lightning in TB 1.5, we'll just use the current time.
*/
function getUUID() {
if ("@mozilla.org/uuid-generator;1" in Components.classes) {
var uuidGen = Components.classes["@mozilla.org/uuid-generator;1"].
getService(Components.interfaces.nsIUUIDGenerator);
// generate uuids without braces to avoid problems with
// CalDAV servers that don't support filenames with {}
return uuidGen.generateUUID().toString().replace(/[{}]/g, '');
}
// No uuid service (we're on the 1.8.0 branch)
return "uuid" + (new Date()).getTime();
}
/** Due to a bug in js-wrapping, normal == comparison can fail when we
* have 2 objects. Use these functions to force them both to get wrapped
* the same way, allowing for normal comparison.
*/
/**
* calIItemBase comparer
*/
function compareItems(aItem, aOtherItem) {
var sip1 = Components.classes["@mozilla.org/supports-interface-pointer;1"].
createInstance(Components.interfaces.nsISupportsInterfacePointer);
sip1.data = aItem;
sip1.dataIID = Components.interfaces.calIItemBase;
var sip2 = Components.classes["@mozilla.org/supports-interface-pointer;1"].
createInstance(Components.interfaces.nsISupportsInterfacePointer);
sip2.data = aOtherItem;
sip2.dataIID = Components.interfaces.calIItemBase;
return sip1.data == sip2.data;
}
/**
* Generic object comparer
* Use to compare two objects which are not of type calIItemBase, in order
* to avoid the js-wrapping issues mentioned above.
*
* @param aObject first object to be compared
* @param aOtherObject second object to be compared
* @param aIID IID to use in comparison, undefined/null defaults to nsISupports
*/
function compareObjects(aObject, aOtherObject, aIID) {
// xxx todo: seems to work fine e.g. for WCAP, but I still mistrust this trickery...
// Anybody knows an official API that could be used for this purpose?
// For what reason do clients need to pass aIID since
// every XPCOM object has to implement nsISupports?
// XPCOM (like COM, like UNO, ...) defines that QueryInterface *only* needs to return
// the very same pointer for nsISupports during its lifetime.
if (!aIID) {
aIID = Components.interfaces.nsISupports;
}
var sip1 = Components.classes["@mozilla.org/supports-interface-pointer;1"].
createInstance(Components.interfaces.nsISupportsInterfacePointer);
sip1.data = aObject;
sip1.dataIID = aIID;
var sip2 = Components.classes["@mozilla.org/supports-interface-pointer;1"].
createInstance(Components.interfaces.nsISupportsInterfacePointer);
sip2.data = aOtherObject;
sip2.dataIID = aIID;
return sip1.data == sip2.data;
}
/**
* Compare two arrays using the passed function.
*/
function compareArrays(aOne, aTwo, compareFunc) {
if (!aOne && !aTwo)
return true;
if (!aOne || !aTwo)
return false;
var len = aOne.length;
if (len != aTwo.length)
return false;
for (var i = 0; i < len; ++i) {
if (!compareFunc(aOne[i], aTwo[i]))
return false;
}
return true;
}
/**
* Ensures the passed IID is in the list, else throws Components.results.NS_ERROR_NO_INTERFACE.
*/
function ensureIID(aList, aIID) {
for each (var iid in aList) {
if (aIID.equals(iid))
return;
}
throw Components.results.NS_ERROR_NO_INTERFACE
}
/**
* Takes care of all QueryInterface business, including calling the QI of any
* existing parent prototypes.
*
* @param aSelf The object the QueryInterface is being made to
* @param aProto Caller's prototype object
* @param aIID The IID to check for
* @param aList (Optional if aClassInfo is specified) An array of
* interfaces from Components.interfaces
* @param aClassInfo (Optional) an Object containing the class info for this
* prototype.
*/
function doQueryInterface(aSelf, aProto, aIID, aList, aClassInfo) {
if (aClassInfo && aIID.equals(Components.interfaces.nsIClassInfo)) {
return aClassInfo;
}
var list = aList;
if (!list && aClassInfo) {
list = aClassInfo.getInterfaces({});
}
var list = aList;
if (!list && aClassInfo) {
list = aClassInfo.getInterfaces({});
}
for each (var iid in list) {
if (aIID.equals(iid))
return aSelf;
}
var base = aProto.__proto__;
if (base && base.QueryInterface) {
// Try to QI the base prototype
return base.QueryInterface.call(aSelf, aIID);
}
throw Components.results.NS_ERROR_NO_INTERFACE;
}
/**
* Many computations want to work only with date-times, not with dates. This
* method will return a proper datetime (set to midnight) for a date object. If
* the object is already a datetime, it will simply be returned.
*
* @param aDate the date or datetime to check
*/
function ensureDateTime(aDate) {
if (!aDate || !aDate.isDate) {
return aDate;
}
var newDate = aDate.clone();
newDate.isDate = false;
return newDate;
}
/****
**** debug code
****/
/**
* Logs a string or an object to both stderr and the js-console only in the case
* where the calendar.debug.log pref is set to true.
*
* @param aArg either a string to log or an object whose entire set of
* properties should be logged.
*/
function LOG(aArg) {
var prefB = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
var shouldLog = false;
try {
shouldLog = prefB.getBoolPref("calendar.debug.log");
} catch(ex) {}
if (!shouldLog) {
return;
}
ASSERT(aArg, "Bad log argument.", false);
var string;
// We should just dump() both String objects, and string primitives.
if (!(aArg instanceof String) && !(typeof(aArg) == "string")) {
var string = "Logging object...\n";
for (var prop in aArg) {
string += prop + ': ' + aArg[prop] + '\n';
}
string += "End object\n";
} else {
string = aArg;
}
dump(string + '\n');
var consoleSvc = Components.classes["@mozilla.org/consoleservice;1"].
getService(Components.interfaces.nsIConsoleService);
consoleSvc.logStringMessage(string);
}
/**
* Dumps a warning to both console and js console.
*
* @param aMessage warning message
*/
function WARN(aMessage) {
dump("Warning: " + aMessage + '\n');
var scriptError = Components.classes["@mozilla.org/scripterror;1"]
.createInstance(Components.interfaces.nsIScriptError);
scriptError.init(aMessage, null, null, 0, 0,
Components.interfaces.nsIScriptError.warningFlag,
"component javascript");
var consoleSvc = Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService);
consoleSvc.logMessage(scriptError);
}
/**
* Returns a string describing the current js-stack with filename and line
* numbers.
*
* @param aDepth (optional) The number of frames to include. Defaults to 5.
*/
function STACK(aDepth) {
var depth = aDepth || 5;
var stack = "";
var frame = Components.stack.caller;
for (var i = 1; i <= depth && frame; i++) {
stack += i + ": [" + frame.filename + ":" +
frame.lineNumber + "] " + frame.name + "\n";
frame = frame.caller;
}
return stack;
}
/**
* Logs a message and the current js-stack, if aCondition fails
*
* @param aCondition the condition to test for
* @param aMessage the message to report in the case the assert fails
* @param aCritical if true, throw an error to stop current code execution
* if false, code flow will continue
*/
function ASSERT(aCondition, aMessage, aCritical) {
if (aCondition) {
return;
}
var string = "Assert failed: " + aMessage + '\n' + STACK();
if (aCritical) {
throw new Error(string);
} else {
Components.utils.reportError(string);
}
}
/**
* Uses the prompt service to display an error message.
*
* @param aMsg The message to be shown
*/
function showError(aMsg) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
promptService.alert(window,
calGetString("calendar", "errorTitle"),
aMsg);
}
/**
* Auth prompt implementation - Uses password manager if at all possible.
*/
function calAuthPrompt() {
// use the window watcher service to get a nsIAuthPrompt impl
this.mPrompter = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.getNewAuthPrompter(null);
this.mTriedStoredPassword = false;
}
calAuthPrompt.prototype = {
prompt: function capP(aDialogTitle, aText, aPasswordRealm, aSavePassword,
aDefaultText, aResult) {
return this.mPrompter.prompt(aDialogTitle, aText, aPasswordRealm,
aSavePassword, aDefaultText, aResult);
},
getPasswordInfo: function capGPI(aPasswordRealm) {
var username;
var password;
var found = false;
if ("@mozilla.org/passwordmanager;1" in Components.classes) {
var passwordManager = Components.classes["@mozilla.org/passwordmanager;1"]
.getService(Components.interfaces.nsIPasswordManager);
var passwordRealm = aPasswordRealm.passwordRealm || aPasswordRealm;
var pwenum = passwordManager.enumerator;
// step through each password in the password manager until we find the one we want:
while (pwenum.hasMoreElements()) {
try {
var pass = pwenum.getNext().QueryInterface(Components.interfaces.nsIPassword);
if (pass.host == passwordRealm) {
// found it!
username = pass.user;
password = pass.password;
found = true;
}
} catch (ex) {
found = true;
break;
// don't do anything here, ignore the password that could not
// be read
}
}
} else {
var loginManager = Components.classes["@mozilla.org/login-manager;1"]
.getService(Components.interfaces
.nsILoginManager);
var logins = loginManager.findLogins({}, aPasswordRealm.prePath, null,
aPasswordRealm.realm);
if (logins.length) {
username = logins[0].username;
password = logins[0].password;
found = true;
}
}
return {found: found, username: username, password: password};
},
promptUsernameAndPassword: function capPUAP(aDialogTitle, aText,
aPasswordRealm,aSavePassword,
aUser, aPwd) {
var pw;
if (!this.mTriedStoredPassword) {
pw = this.getPasswordInfo(aPasswordRealm);
}
if (pw && pw.found) {
this.mTriedStoredPassword = true;
aUser.value = pw.username;
aPwd.value = pw.password;
return true;
} else {
return this.mPrompter.promptUsernameAndPassword(aDialogTitle, aText,
aPasswordRealm,
aSavePassword,
aUser, aPwd);
}
},
// promptAuth is needed/used on trunk only
promptAuth: function capPA(aChannel, aLevel, aAuthInfo) {
var hostRealm = {};
hostRealm.prePath = aChannel.URI.prePath;
hostRealm.realm = aAuthInfo.realm;
hostRealm.passwordRealm = aChannel.URI.host + ":" + aChannel.URI.port +
" (" + aAuthInfo.realm + ")";
var pw;
if (!this.mTriedStoredPassword) {
pw = this.getPasswordInfo(hostRealm);
}
if (pw && pw.found) {
this.mTriedStoredPassword = true;
aAuthInfo.username = pw.username;
aAuthInfo.password = pw.password;
return true;
} else {
var prompter2 =
Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIPromptFactory)
.getPrompt(null, Components.interfaces.nsIAuthPrompt2);
return prompter2.promptAuth(aChannel, aLevel, aAuthInfo);
}
},
promptPassword: function capPP(aDialogTitle, aText, aPasswordRealm,
aSavePassword, aPwd) {
var found = false;
var pw;
if (!this.mTriedStoredPassword) {
pw = this.getPasswordInfo(aPasswordRealm);
}
if (pw && pw.found) {
this.mTriedStoredPassword = true;
aPwd.value = pw.password;
return true;
} else {
return this.mPrompter.promptPassword(aDialogTitle, aText,
aPasswordRealm, aSavePassword,
aPwd);
}
}
}
/**
* Pick whichever of "black" or "white" will look better when used as a text
* color against a background of bgColor.
*
* @param bgColor the background color as a "#RRGGBB" string
*/
function getContrastingTextColor(bgColor)
{
var calcColor = bgColor.replace(/#/g, "");
var red = parseInt(calcColor.substring(0, 2), 16);
var green = parseInt(calcColor.substring(2, 4), 16);
var blue = parseInt(calcColor.substring(4, 6), 16);
// Calculate the brightness (Y) value using the YUV color system.
var brightness = (0.299 * red) + (0.587 * green) + (0.114 * blue);
// Consider all colors with less than 56% brightness as dark colors and
// use white as the foreground color, otherwise use black.
if (brightness < 144) {
return "white";
}
return "black";
}
/**
* Returns the start date of an item, ie either an event's start date or a task's entry date.
*/
function calGetStartDate(aItem)
{
return (isEvent(aItem) ? aItem.startDate : aItem.entryDate);
}
/**
* Returns the end date of an item, ie either an event's end date or a task's due date.
*/
function calGetEndDate(aItem)
{
return (isEvent(aItem) ? aItem.endDate : aItem.dueDate);
}
/**
* Checks whether the passed item fits into the demanded range.
*
* @param item the item
* @param rangeStart (inclusive) range start or null (open range)
* @param rangeStart (exclusive) range end or null (open range)
* @param returnDtstartOrDue returns item's start (or due) date in case
* the item is in the specified Range; null otherwise.
*/
function checkIfInRange(item, rangeStart, rangeEnd, returnDtstartOrDue)
{
var startDate;
var endDate;
if (isEvent(item)) {
startDate = item.startDate;
if (!startDate) { // DTSTART mandatory
// xxx todo: should we assert this case?
return null;
}
endDate = (item.endDate || startDate);
} else {
var dueDate = item.dueDate;
startDate = (item.entryDate || dueDate);
if (!startDate) {
if (returnDtstartOrDue) { // DTSTART or DUE mandatory
return null;
}
// 3.6.2. To-do Component
// A "VTODO" calendar component without the "DTSTART" and "DUE" (or
// "DURATION") properties specifies a to-do that will be associated
// with each successive calendar date, until it is completed.
var completedDate = item.completedDate;
if (completedDate) {
var queryStart = ensureDateTime(rangeStart);
completedDate = ensureDateTime(completedDate);
return (!queryStart || completedDate.compare(queryStart) > 0);
}
return true;
}
endDate = (dueDate || startDate);
}
var start = ensureDateTime(startDate);
var end = ensureDateTime(endDate);
var queryStart = ensureDateTime(rangeStart);
var queryEnd = ensureDateTime(rangeEnd);
if (start.compare(end) == 0) {
if ((!queryStart || start.compare(queryStart) >= 0) &&
(!queryEnd || start.compare(queryEnd) < 0)) {
return startDate;
}
} else {
if ((!queryEnd || start.compare(queryEnd) < 0) &&
(!queryStart || end.compare(queryStart) > 0)) {
return startDate;
}
}
return null;
}
/**
* Returns true if we are Sunbird (according to our UUID), false otherwise.
*/
function isSunbird()
{
const kSUNBIRD_UID = "{718e30fb-e89b-41dd-9da7-e25a45638b28}";
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
getService(Components.interfaces.nsIXULAppInfo);
return appInfo.ID == kSUNBIRD_UID;
}
function hasPositiveIntegerValue(elementId)
{
var value = document.getElementById(elementId).value;
if (value && (parseInt(value) == value) && value > 0) {
return true;
}
return false;
}
function getAtomFromService(aStr) {
var atomService = Components.classes["@mozilla.org/atom-service;1"]
.getService(Components.interfaces.nsIAtomService);
return atomService.getAtom(aStr);
}
function calInterfaceBag(iid) {
this.init(iid);
}
calInterfaceBag.prototype = {
mIid: null,
mInterfaces: null,
/// internal:
init: function calInterfaceBag_init(iid) {
this.mIid = iid;
this.mInterfaces = [];
},
/// external:
get size() {
return this.mInterfaces.length;
},
get interfaceArray() {
return this.mInterfaces;
},
add: function calInterfaceBag_add(iface) {
if (iface) {
var iid = this.mIid;
function eq(obj) {
return compareObjects(obj, iface, iid);
}
if (!this.mInterfaces.some(eq)) {
this.mInterfaces.push(iface);
}
}
},
remove: function calInterfaceBag_remove(iface) {
if (iface) {
var iid = this.mIid;
function neq(obj) {
return !compareObjects(obj, iface, iid);
}
this.mInterfaces = this.mInterfaces.filter(neq);
}
},
forEach: function calInterfaceBag_forEach(func) {
this.mInterfaces.forEach(func);
}
};
function calListenerBag(iid) {
this.init(iid);
}
calListenerBag.prototype = {
__proto__: calInterfaceBag.prototype,
notify: function calListenerBag_notify(func, args) {
function notifyFunc(iface) {
try {
iface[func].apply(iface, args ? args : []);
}
catch (exc) {
Components.utils.reportError(exc + " STACK: " + STACK());
}
}
this.mInterfaces.forEach(notifyFunc);
}
};
function sendMailTo(aRecipient, aSubject, aBody) {
if (Components.classes["@mozilla.org/messengercompose;1"]) {
// We are in Thunderbird, we can use the compose interface directly
var msgComposeService = Components.classes["@mozilla.org/messengercompose;1"]
.getService(Components.interfaces.nsIMsgComposeService);
var msgParams = Components.classes["@mozilla.org/messengercompose/composeparams;1"]
.createInstance(Components.interfaces.nsIMsgComposeParams);
var composeFields = Components.classes["@mozilla.org/messengercompose/composefields;1"]
.createInstance(Components.interfaces.nsIMsgCompFields);
composeFields.to = aRecipient;
composeFields.subject = aSubject;
composeFields.body = aBody;
msgParams.type = Components.interfaces.nsIMsgCompType.New;
msgParams.format = Components.interfaces.nsIMsgCompFormat.Default;
msgParams.composeFields = composeFields;
msgComposeService.OpenComposeWindowWithParams(null, msgParams);
} else {
// We are in a place without a composer. Use the external protocol
// service.
var protoSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
.getService(Components.interfaces.nsIExternalProtocolService);
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var uriString = "mailto:";
var uriParams = [];
if (aRecipient) {
uriString += aRecipient;
}
if (aSubject) {
uriParams.push("subject=" + encodeURIComponent(aSubject));
}
if (aBody) {
uriParams.push("body=" + encodeURIComponent(aSubject));
}
if (uriParams.length > 0) {
uriString += "?" + uriParams.join("&");
}
protoSvc.loadUrl(ioService.newURI(uriString, null, null));
}
}
/**
* This object implements calIOperation and could group multiple sub
* operations into one. You can pass a cancel function which is called once
* the operation group is cancelled.
* Users must call notifyCompleted() once all sub operations have been
* successful, else the operation group will stay pending.
* The reason for the latter is that providers currently should (but need
* not) implement (and return) calIOperation handles, thus there may be pending
* calendar operations (without handle).
*/
function calOperationGroup(cancelFunc) {
this.wrappedJSObject = this;
if (calOperationGroup.mOpGroupId === undefined) {
calOperationGroup.mOpGroupId = 0;
}
if (calOperationGroup.mOpGroupPrefix === undefined) {
calOperationGroup.mOpGroupPrefix = (getUUID() + "-");
}
this.mCancelFunc = cancelFunc;
this.mId = (calOperationGroup.mOpGroupPrefix + calOperationGroup.mOpGroupId++);
this.mSubOperations = [];
}
calOperationGroup.prototype = {
mCancelFunc: null,
mId: null,
mIsPending: true,
mStatus: Components.results.NS_OK,
mSubOperations: null,
add: function calOperationGroup_add(op) {
if (op && op.isPending) {
this.mSubOperations.push(op);
}
},
remove: function calOperationGroup_remove(op) {
if (op) {
function filterFunc(op_) {
return (op.id != op_.id);
}
this.mSubOperations = this.mSubOperations.filter(filterFunc);
}
},
get isEmpty() {
return (this.mSubOperations.length == 0);
},
notifyCompleted: function calOperationGroup_notifyCompleted(status) {
ASSERT(this.isPending, "[calOperationGroup_notifyCompleted] this.isPending");
if (this.isPending) {
this.mIsPending = false;
if (status) {
this.mStatus = status;
}
}
},
toString: function calOperationGroup_toString() {
return ("[calOperationGroup] id=" + this.id);
},
// calIOperation:
get id() {
return this.mId;
},
get isPending() {
return this.mIsPending;
},
get status() {
return this.mStatus;
},
cancel: function calOperationGroup_cancel(status) {
if (this.isPending) {
if (!status) {
status = Components.interfaces.calIErrors.OPERATION_CANCELLED;
}
this.notifyCompleted(status);
var cancelFunc = this.mCancelFunc;
if (cancelFunc) {
this.mCancelFunc = null;
cancelFunc();
}
var subOperations = this.mSubOperations;
this.mSubOperations = [];
function forEachFunc(op) {
op.cancel(Components.interfaces.calIErrors.OPERATION_CANCELLED);
}
subOperations.forEach(forEachFunc);
}
}
};
function sameDay(date1, date2) {
if (date1 && date2) {
if ((date1.day == date2.day) &&
(date1.month == date2.month) &&
(date1.year == date2.year)) {
return true;
}
}
return false;
}
/**
* Centralized funtions for accessing prodid and version
*/
function calGetProductId() {
return "-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN";
}
function calGetProductVersion() {
return "2.0";
}
/**
* This is a centralized function for setting the prodid and version on an
* ical component. This should be used whenever you need to set the prodid
* and version on a calIcalComponent object.
*
* @param
* aIcalComponent The ical component to set the prodid and version on.
*/
function calSetProdidVersion(aIcalComponent) {
// Throw for an invalid parameter
if (!(aIcalComponent instanceof Components.interfaces.calIIcalComponent)) {
throw Components.results.NS_ERROR_INVALID_ARG;
}
// Set the prodid and version
aIcalComponent.prodid = calGetProductId();
aIcalComponent.version = calGetProductVersion();
}
/**
* This function returns a sibling of a XUL element, that is positioned behind
* it in the DOM hierarchy *
* @param
* aElement The XUL element to derive the sibling from
* @param
* aDistance An integer value denoting how the relative position
* of the returned sibling within the parent container
*/
function getAdjacentSibling(aElement, aDistance) {
var retElement = aElement;
if (aDistance > 0) {
for (var i = 0; i < aDistance; i++) {
if (retElement) {
try {
retElement = retElement.nextSibling;
} catch (e) {
retElement = null;
i = aDistance;
}
}
}
}
return retElement;
}
/**
* deeply clones a popupmenu
*
* @param aMenuPopupId The Id of the popup-menu to be cloned
* @param aNewPopupId The new id of the cloned popup-menu
* @param aNewIdPrefix To keep the ids unique the childnodes of the returned
* popup-menu are prepended with a prefix
* @return the cloned popup-menu
*/
function clonePopupMenu(aMenuPopupId, aNewPopupId, aNewIdPrefix) {
var oldMenuPopup = document.getElementById(aMenuPopupId);
var retMenuPopup = oldMenuPopup.cloneNode(true);
retMenuPopup.setAttribute("id", aNewPopupId);
var menuElements = retMenuPopup.getElementsByAttribute("id", "*");
for (var i = 0; i < menuElements.length; i++) {
var lid = menuElements[i].getAttribute("id");
menuElements[i].setAttribute("id", aNewIdPrefix + lid);
}
return retMenuPopup;
}
/**
* applies a value to all children of a Menu. If the respective childnodes define
* a command the value is applied to the attribute of thecommand of the childnode
*
* @param aElement The parentnode of the elements
* @param aAttributeName The name of the attribute
* @param aValue The value of the attribute
*/
function applyAttributeToMenuChildren(aElement, aAttributeName, aValue) {
var sibling = aElement.firstChild;
do {
if (sibling) {
var domObject = sibling;
var commandName = null;
if (sibling.hasAttribute("command")){
commandName = sibling.getAttribute("command");
}
if (commandName) {
var command = document.getElementById(commandName);
if (command) {
domObject = command;
}
}
domObject.setAttribute(aAttributeName, aValue);
sibling = sibling.nextSibling;
}
} while (sibling);
}
/**
* compares the value of a property of an array of objects and returns
* true or false if it is same or not among all array members
*
* @param aObjects An Array of Objects to inspect
* @param aProperty Name the name of the Property of which the value is compared
*/
function isPropertyValueSame(aObjects, aPropertyName) {
var value = null;
for (var i = 0; i < aObjects.length; i++) {
if (!value) {
value = aObjects[0][aPropertyName];
}
var compValue = aObjects[i][aPropertyName];
if (compValue != value ) {
return false;
}
}
return true;
}
/**
* sets the value of a boolean attribute by either setting the value or
* removing the attribute
*
* @param aXulElement The XulElement the attribute is applied to
* @param aAttribute the name of the attribute
* @param aValue the boolean value
*/
function setBooleanAttribute(aXulElement, aAttribute, aValue) {
if (aXulElement) {
if (aValue) {
aXulElement.setAttribute(aAttribute, "true");
}
else {
if (aXulElement.hasAttribute(aAttribute)) {
aXulElement.removeAttribute(aAttribute);
}
}
}
}
function removeAnonymousElement(aParentNode, aId) {
var child = document.getAnonymousElementByAttribute(aParentNode, "anonid", aId);
child.parentNode.removeChild(child);
}
function getParentNode(aNode, aLocalName) {
var node = aNode;
do {
node = node.parentNode;
} while (node && (node.localName != aLocalName));
return node;
}
function setItemProperty(item, propertyName, aValue, aCapability) {
var isSupported = (item.calendar.getProperty("capabilities." + aCapability + ".supported") !== false)
var value = (aCapability && !isSupported ? null : aValue);
switch (propertyName) {
case "startDate":
if (value.isDate && !item.startDate.isDate ||
!value.isDate && item.startDate.isDate ||
!compareObjects(value.timezone, item.startDate.timezone) ||
value.compare(item.startDate) != 0) {
item.startDate = value;
}
break;
case "endDate":
if (value.isDate && !item.endDate.isDate ||
!value.isDate && item.endDate.isDate ||
!compareObjects(value.timezone, item.endDate.timezone) ||
value.compare(item.endDate) != 0) {
item.endDate = value;
}
break;
case "entryDate":
if (value == item.entryDate) {
break;
}
if (value && !item.entryDate ||
!value && item.entryDate ||
value.isDate != item.entryDate.isDate ||
!compareObjects(value.timezone, item.entryDate.timezone) ||
value.compare(item.entryDate) != 0) {
item.entryDate = value;
}
break;
case "dueDate":
if (value == item.dueDate) {
break;
}
if (value && !item.dueDate ||
!value && item.dueDate ||
value.isDate != item.dueDate.isDate ||
!compareObjects(value.timezone, item.dueDate.timezone) ||
value.compare(item.dueDate) != 0) {
item.dueDate = value;
}
break;
case "isCompleted":
if (value != item.isCompleted) {
item.isCompleted = value;
}
break;
case "title":
if (value != item.title) {
item.title = value;
}
break;
default:
if (!value || value == "") {
item.deleteProperty(propertyName);
} else if (item.getProperty(propertyName) != value) {
item.setProperty(propertyName, value);
}
break;
}
}
/**
* Implements a property bag.
*/
function calPropertyBag() {
this.mData = {};
}
calPropertyBag.prototype = {
mData: null,
setProperty: function cpb_setProperty(aName, aValue) {
this.mData[aName] = aValue;
},
getProperty: function cpb_getProperty(aName) {
var aValue = this.mData[aName];
if (aValue === undefined) {
aValue = null;
}
return aValue;
},
deleteProperty: function cpb_deleteProperty(aName) {
delete this.mData[aName];
},
get enumerator() {
return new calPropertyBagEnumerator(this);
}
};
// implementation part of calPropertyBag
function calPropertyBagEnumerator(bag) {
this.mIndex = 0;
this.mBag = bag;
var keys = [];
for (var key in bag.mData) {
keys.push(key);
}
this.mKeys = keys;
}
calPropertyBagEnumerator.prototype = {
mIndex: 0,
mBag: null,
mKeys: null,
// nsISimpleEnumerator:
getNext: function cpb_enum_getNext() {
if (!this.hasMoreElements()) { // hasMoreElements is called by intention to skip yet deleted properties
ASSERT(false, Components.results.NS_ERROR_UNEXPECTED);
throw Components.results.NS_ERROR_UNEXPECTED;
}
var name = this.mKeys[this.mIndex++];
return { // nsIProperty:
QueryInterface: function cpb_enum_prop_QueryInterface(aIID) {
ensureIID([Components.interfaces.nsIProperty, Components.interfaces.nsISupports], aIID);
return this;
},
name: name,
value: this.mCurrentValue
};
},
hasMoreElements: function cpb_enum_hasMoreElements() {
while (this.mIndex < this.mKeys.length) {
this.mCurrentValue = this.mBag.mData[this.mKeys[this.mIndex]];
if (this.mCurrentValue !== undefined) {
return true;
}
++this.mIndex;
}
return false;
}
};
// Send iTIP invitation
function sendItipInvitation(aItem) {
// XXX Until we rethink attendee support and until such support
// is worked into the event dialog (which has been done in the prototype
// dialog to a degree) then we are going to simply hack in some attendee
// support so that we can round-trip iTIP invitations.
// Since there is no way to determine the type of transport an
// attendee requires, we default to email
var emlSvc = Components.classes["@mozilla.org/calendar/itip-transport;1?type=email"]
.createInstance(Components.interfaces.calIItipTransport);
var itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
.createInstance(Components.interfaces.calIItipItem);
var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService);
var sb = sbs.createBundle("chrome://lightning/locale/lightning.properties");
var recipients = [];
// We have to modify our item a little, so we clone it.
var item = aItem.clone();
// Fix up our attendees for invitations using some good defaults
itemAtt = item.getAttendees({}); // reuse cloned attendees
item.removeAllAttendees();
for each (var attendee in itemAtt) {
attendee.role = "REQ-PARTICIPANT";
attendee.participationStatus = "NEEDS-ACTION";
attendee.rsvp = true;
item.addAttendee(attendee);
recipients.push(attendee);
}
// XXX The event dialog has no means to set us as the organizer
// since we defaulted to email above, we know we need to prepend
// mailto when we convert it to an attendee
// This also means that when we are Updating an event, we will be making
// a blatant assumption that you (the updater) are the organizer of the event.
// This is probably ok since we don't support the iTIP COUNTER method,
// but it would be better if we didn't allow you to modify an event that you
// are not the organizer of and send out invitations to it as if you were.
// For this support, we'll need a real invitation manager component.
var organizer = Components.classes["@mozilla.org/calendar/attendee;1"]
.createInstance(Components.interfaces.calIAttendee);
organizer.id = "mailto:" + emlSvc.defaultIdentity;
organizer.role = "REQ-PARTICIPANT";
organizer.participationStatus = "ACCEPTED";
organizer.isOrganizer = true;
// Add our organizer to the item. Again, the event dialog really doesn't
// have a mechanism for creating an item with a method, so let's add
// that too while we're at it. We'll also fake Sequence ID support.
item.organizer = organizer;
item.setProperty("METHOD", "REQUEST");
item.setProperty("SEQUENCE", item.generation);
var summary
if (item.getProperty("SUMMARY")) {
summary = item.getProperty("SUMMARY");
} else {
summary = "";
}
// Initialize and set our properties on the item
itipItem.init(item.icalString);
itipItem.isSend = true;
itipItem.receivedMethod = "REQUEST";
itipItem.responseMethod = "REQUEST";
itipItem.autoResponse = Components.interfaces.calIItipItem.USER;
// Get ourselves some default text - when we handle organizer properly
// We'll need a way to configure the Common Name attribute and we should
// use it here rather than the email address
var subject = sb.formatStringFromName("itipRequestSubject",
[summary], 1);
var body = sb.formatStringFromName("itipRequestBody",
[emlSvc.defaultIdentity, summary],
2);
// Send it!
emlSvc.sendItems(recipients.length, recipients, subject, body, itipItem);
}